/*
 * \brief  Child creation framework
 * \author Norman Feske
 * \date   2006-07-22
 */

/*
 * Copyright (C) 2006-2009 Norman Feske
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#ifndef _INCLUDE__BASE__CHILD_H_
#define _INCLUDE__BASE__CHILD_H_

#include <parent/server.h>
#include <ram_session/client.h>
#include <util/arg_string.h>
#include <base/env.h>
#include <base/server.h>
#include <base/heap.h>
#include <base/process.h>
#include <base/service.h>
#include <base/lock.h>

namespace Genode {

	class Child;


	/*
	 * There are three possible cases of how a session is provided:
	 *
	 * # The service is implemented locally.
	 * # The session was obtained by asking our parent.
	 * # The session is provided by one of our children.
	 *
	 * These types must be differentiated for the quota management
	 * when a child issues the closing of a session or a transfers
	 * quota via our parent interface.
	 *
	 * :Closing a session:
	 *
	 *   If we close a session to a local service, we transfer the
	 *   session quota from our own account to the client.
	 *
	 *   If we close a parent session, we receive the session
	 *   quota on our own account and must transfer this amount
	 *   to the session-closing child.
	 *
	 *   If we close a session provided by a server child,
	 *   we close the session at the server, transfer the session
	 *   quota from the server's ram session to our account, and
	 *   subsequently transfer the same amount from our account to
	 *   the client.
	 */
	class Session : public Object_pool<Session>::Entry, public List<Session>::Element
	{
		private:

			enum { IDENT_LEN = 16 };

			Session_control   *_session_control;
			Session_capability _cap;
			size_t             _donated_ram_quota;
			Child             *_server;
			char               _ident[IDENT_LEN];   /* identifier for debugging */

		public:

			/**
			 * Constructor
			 */
			Session(Session_capability session, Session_control *sc,
			        size_t ram_quota, Child *server = 0, const char *ident = "<noname>")
			:
				Object_pool<Session>::Entry(session), _session_control(sc),
				_cap(session), _donated_ram_quota(ram_quota),
				_server(server)
			{
				strncpy(_ident, ident, sizeof(_ident));
			}

			/**
			 * Default constructor creates invalid session
			 */
			Session() : _session_control(0) { }

			/**
			 * Accessors
			 */
			Session_capability cap()               { return _cap; }
			Session_control   *session_control()   { return _session_control; }
			size_t             donated_ram_quota() { return _donated_ram_quota; }
			bool               valid()             { return _session_control != 0; }
			Child             *server()            { return _server; }
			const char        *ident()             { return _ident; }
	};


	/*
	 * For each child, we create a separate entry point that serves
	 * the parent interface for this child only. This way, we can
	 * do blocking capability invocations on request of the child
	 * while still maintaining the service to other children that
	 * have independent entry points.
	 */
	class Child : public Parent_server, public Client
	{
		protected:

			/* ram session that contains the quota of the child */
			Ram_session_capability  _ram_session_cap;
			Ram_session_client      _ram_session_client;

		private:

			/* heap for child-specific allocations using the child's quota */
			Heap                    _heap;

			Msgbuf<256>             _snd_msg, _rcv_msg;
			Server_activation<8192> _server_activation;
			Server_entrypoint       _server_entrypoint;
			Parent_capability       _parent_cap;

			/* cpu session that contains the quota of the child */
			Cpu_session_capability  _cpu_session_cap;

			Process                 _process;

			/* sessions opened by the child */
			Object_pool<Session>    _session_pool;
			List<Session>           _session_list;
			Lock                    _lock;          /* protect list manipulation */

		protected:

			/**
			 * Attach session information to a child
			 *
			 * \throw Ram_session::Quota_exceeded  the child's heap partition cannot
			 *                                     hold the session meta data
			 */
			void _add_session(Session s)
			{
				Lock::Guard lock_guard(_lock);

				/*
				 * Store session information in a new child's meta data
				 * structure.  The allocation from 'heap()' may throw a
				 * 'Ram_session::Quota_exceeded' exception.
				 */
				Session *session = 0;
				try {
					session = new (heap())
					          Session(s.cap(), s.session_control(),
					                  s.donated_ram_quota(), s.server(), s.ident());
				} catch (Allocator::Out_of_memory) {
					throw Ram_session::Quota_exceeded();
				}

				/* these functions may also throw 'Ram_session::Quota_exceeded' */
				_session_pool.insert(session);
				_session_list.insert(session);
			}

			void _remove_session(Session *s)
			{
				Lock::Guard lock_guard(_lock);

				/* forget about this session */
				_session_pool.remove(s);
				_session_list.remove(s);

				/* return session quota to the ram session of the child */
				if (env()->ram_session()->transfer_quota(_ram_session_cap, s->donated_ram_quota()))
					PERR("We ran out of our own quota");

				destroy(heap(), s);
			}

		public:

			/**
			 * Constructor
			 *
			 * \param name         unique name of the child
			 * \param elf_ds       dataspace containing the binary
			 * \param ram_session  RAM session with the child's quota
			 * \param cpu_session  CPU session with the child's quota
			 * \param cap_session  capability allocator
			 * \param args         not used
			 */
			Child(const char             *name,
			      Dataspace_capability    elf_ds,
			      Ram_session_capability  ram_session,
			      Cpu_session_capability  cpu_session,
			      Cap_session            *cap_session,
			      char                   *args[])
			:
				_ram_session_cap(ram_session),
				_ram_session_client(ram_session),
				_heap(&_ram_session_client, env()->rm_session()),
				_server_activation(&_snd_msg, &_rcv_msg, false, name),
				_server_entrypoint(cap_session, &_server_activation),
				_parent_cap(_server_entrypoint.manage(this)),
				_cpu_session_cap(cpu_session),
				_process(elf_ds, ram_session, cpu_session, _parent_cap, name, 0)
			{ }

			/**
			 * Destructor
			 *
			 * On destruction of a child, we close all sessions
			 * of the child to other services.
			 */
			~Child()
			{
				Session *s;
				while ((s = _session_pool.first()))
					close(s->cap());
			}

			/**
			 * Return heap that uses the child's quota
			 */
			Allocator *heap() { return &_heap; }

			Ram_session_capability ram_session_cap() { return _ram_session_cap; }
			Cpu_session_capability cpu_session_cap() { return _cpu_session_cap; }
			Rm_session_capability  rm_session_cap()  { return _process.rm_session_cap(); }

			/**
			 * Finalize construction of child
			 *
			 * On construction of the child, we create a new protection domain
			 * and a new parent server thread. The new child starts immediately
			 * to speak with its parent server thread and performs 'session()'
			 * RPC calls. Therefore, the dispatch function of the parent server
			 * may be called before the complete child object (including its
			 * final vtable entries) is constructed. Therefore, we delay the
			 * server activation of the parent server until all constructors
			 * are completed.
			 */
			void finalize_construction() { _server_activation.start(); }

			/**
			 * Prepare destruction of child
			 *
			 * Similar to the construction case we must ensure that the server
			 * activation will not use half-destructed objects (vtable).
			 * Therefore we dissolve the server object (this child) from
			 * entrypoint before destruction starts.
			 */
			void prepare_destruction() { _server_entrypoint.dissolve(this); }

			/**
			 * Discard all sessions to specified server
			 *
			 * When this function is called, we assume the server protection
			 * domain to be dead and all that all server quote was already
			 * transfered back to our own 'env()->ram_session()' account. Note
			 * that the specified server object may not exist anymore. We do
			 * not de-reference the server argument in here!
			 */
			void revoke_server(Child *server)
			{
				while (1) {

					/* search session to server */
					Session *s = _session_list.first();
					for ( ; s && (s->server() != server); s = s->next());

					/* if no matching session exists, we are done */
					if (!s) return;

					_remove_session(s);
				}
			}


			/**
			 * Request entry point used for the child's parent interface
			 *
			 * This function allows for attaching additional server objects
			 * to the child-specific entry point, for example to provide
			 * child-specific services without the need to create separate
			 * entry points and activations. Note that all calls to this
			 * entry point get serialized. Therefore, a blocking server
			 * function defers calls to the parent interface.
			 */
			Server_entrypoint *parent_entrypoint()
			{
				return &_server_entrypoint;
			}


			/**********************
			 ** Parent interface **
			 **********************/

			int announce(const char *service_name, Root_capability service_root) {
				return -1; }

			/*
			 * By default, we only check for the sessions that we created for the child
			 * on startup. A derived class may implement a proper service resolving
			 * policy.
			 */
			Session_capability session(const char *service_name, const char *args)
			{
				/*
				 * Return services that we just created for the child
				 */
				if (strcmp("Env::ram_session", service_name) == 0)
					return _ram_session_cap;
				if (strcmp("Env::cpu_session", service_name) == 0)
					return _cpu_session_cap;
				if (strcmp("Env::rm_session", service_name) == 0)
					return _process.rm_session_cap();
				if (strcmp("Env::pd_session", service_name) == 0)
					return _process.pd_session_cap();

				throw Service_denied();
			}

			void transfer_quota(Session_capability to_session, const char *amount)
			{
				Session *s = _session_pool.obj_by_cap(to_session);

				if (!s) {
					PWRN("no session structure found - nothing to be done\n");
					return;
				}

				size_t ram_quota = Arg_string::find_arg(amount, "ram_quota").ulong_value(0);

				/* transfer quota from client to ourself */
				if (_ram_session_client.transfer_quota(env()->ram_session_cap(), ram_quota)) {
					PWRN("child has insufficient quota to make donation of %zd", ram_quota);
					throw Quota_exceeded();
				}

				/* if server is our child, transfer quota to the server child */
				if (s->server()) {
					if (env()->ram_session()->transfer_quota(s->server()->_ram_session_cap, ram_quota)) {

						PWRN("could not donate to server");

						/* revert quota transfer in the error case */
						env()->ram_session()->transfer_quota(_ram_session_cap, ram_quota);

						/* normally, this should never happen */
						throw Quota_exceeded();
					}
				}

				/* server is either ourself or managed by our parent */
				env()->parent()->transfer_quota(to_session, amount);
			}

			/*
			 * The session may be provided by either the
			 * parent, ourself, or a child of us.
			 */
			void close(Session_capability session_cap)
			{
				/* refuse to close the child's initial sessions */
				if (session_cap.local_name() == _ram_session_cap.local_name()
				 || session_cap.local_name() == _cpu_session_cap.local_name()
				 || session_cap.local_name() == _process.rm_session_cap().local_name()
				 || session_cap.local_name() == _process.pd_session_cap().local_name())
					return;

				Session *s = _session_pool.obj_by_cap(session_cap);

				if (!s) {
					PWRN("no session structure found");
					return;
				}

				/*
				 * There is a chance that the server is not responding to
				 * the 'close' call, making us block infinitely. However,
				 * by using core's cancel-blocking mechanism, we can cancel
				 * the 'close' call by another (watchdog) thread that
				 * invokes 'cancel_blocking' at our thread after a timeout.
				 * The unblocking is reflected at the API level as an
				 * 'Blocking_canceled' exception. We catch this exception
				 * to proceed with normal operation after being unblocked.
				 */
				try {
					s->session_control()->close(s->cap());
				} catch (Blocking_canceled) {
					PDBG("Got Blocking_canceled exception during %s->close call\n", s->ident());
				}

				/*
				 * If the session was provided by a child of us, 'server()'
				 * returns the corresponding child object. Since the session
				 * to the server is closed now, we expect that the server
				 * released all donated resources and we can decrease the
				 * servers' quota.
				 *
				 * If this goes wrong, the server is misbehaving.
				 */
				if (s->server()) {
					Ram_session *server_ram = &s->server()->_ram_session_client;
					if (server_ram->transfer_quota(env()->ram_session_cap(),
					                               s->donated_ram_quota())) {
						PERR("Misbehaving server!");
					}
				}

				_remove_session(s);
			}

			void exit(int exit_value)
			{
				PDBG("child exited with exit value %d", exit_value);

				/*
				 * FIXME:
				 *
				 * - Unregister all services of this child while
				 *   closing all corresponding service sessions.
				 * - Close all sessions of this child
				 */
			}
	};
}

#endif /* _INCLUDE__BASE__CHILD_H_ */
